// with event sound and hit!
type mcCallback = (rt: string) => void;

const MOVIECLIP_EVENT_END = 'end';
const MOVIECLIP_EVENT_CANCEL = 'cancel';
const MOVIECLIP_EVENT_ERROR = 'error';
const MOVIECLIP_EVENT_RELEASE = 'release';
const MOVIECLIP_EVENT_SOUND = 'sound';

class MovieClipEx extends egret.MovieClip {
	_pixelHitTest = false;

	public get pixelHitTest(): boolean {
		return this._pixelHitTest;
	}

	public set pixelHitTest(value: boolean) {
		this._pixelHitTest = !!value;
	}

	// 这个判定非常耗时，因为是通过渲染到texture再取texture的alhpa得到！
	override $hitTest(stageX: number, stageY: number): egret.DisplayObject {
		let target = super.$hitTest(stageX, stageY);
		if (target && this._pixelHitTest) {
			const boo = this.hitTestPoint(stageX, stageY, true);
			if (!boo) {
				target = null;
			}
		}
		return target;
	}

	getBoundSizeAll(): number[] {
		// 计算从0到frame
		const offsetPoint = egret.Point.create(0, 0);
		let l = 0;
		let t = 0;
		let r = 0;
		let b = 0;
		for (let frame = 1; frame <= this.totalFrames; frame++) {
			const texture = this.$movieClipData.getTextureByFrame(frame);
			this.$movieClipData.$getOffsetByFrame(frame, offsetPoint);
			if (offsetPoint.x < l) {
				l = offsetPoint.x;
			}
			if (offsetPoint.y < t) {
				t = offsetPoint.y;
			}
			if (offsetPoint.x + texture.$getTextureWidth() > r) {
				r = offsetPoint.x + texture.$getTextureWidth();
			}
			if (offsetPoint.y + texture.$getTextureHeight() > b) {
				b = offsetPoint.y + texture.$getTextureHeight();
			}
		}
		return [l, t, r, b];
	}

	getBoundSizeNow(): number[] {
		const offsetPoint = egret.Point.create(0, 0);
		let l = 0;
		let t = 0;
		let r = 0;
		let b = 0;
		const frame = this.currentFrame;
		// for (let frame = 1; frame <= this.totalFrames; frame++) {
		const texture = this.$movieClipData.getTextureByFrame(frame);
		this.$movieClipData.$getOffsetByFrame(frame, offsetPoint);
		l = offsetPoint.x;
		t = offsetPoint.y;
		r = offsetPoint.x + texture.$getTextureWidth();
		b = offsetPoint.y + texture.$getTextureHeight();
		// }
		return [l, t, r, b];
	}

	// calc scale!
	getBoundBox(): number[] {
		const [l, t, r, b] = this.getBoundSizeNow();
		const width = (r - l) * Math.abs(this.scaleX);
		const height = (b - t) * Math.abs(this.scaleY);

		let anchorOffsetX = -l * Math.abs(this.scaleX);
		if (this.scaleX < 0) {
			anchorOffsetX = width - anchorOffsetX;
		}
		let anchorOffsetY = -t * Math.abs(this.scaleY);
		if (this.scaleY < 0) {
			anchorOffsetY = height - anchorOffsetY;
		}
		return [width, height, anchorOffsetX, anchorOffsetY];
	}

	poolInit(movieClipData: egret.MovieClipData, file: string): void {
		this.callback = null;
		this.endRelease = false;
		this.cb = null;
		this.onhit = 0;
	}

	getFrameByRes(res: number | string): number {
		const data = this.movieClipData;
		let num = 0;
		for (const d of data.frames) {
			if (d.res === res) {
				return num + 1; // MovieClip start from 1 ...
			}
			num++;
		}
		return -1;
	}

	setRes(res: string): void {
		const frame = this.getFrameByRes(res);
		this.gotoAndStop(frame);
	}

	getFrameByIndex(index: number): number {
		// movieClipData.frames 下标是从 1 开始的
		return getStartFrame(this.movieClipData.frames, index);
	}

	togglePlay(): void {
		if (this.$isPlaying) {
			this.stop();
		} else {
			this.play(-1);
		}
	}

	// 注册事件, magic和char都调用
	setEvent(callback: mcCallback): void {
		this.doCallback(MOVIECLIP_EVENT_CANCEL);
		this.callback = callback;
		this.addEventListener(egret.MovieClipEvent.FRAME_LABEL, this.onMovieHit, this);
		this.once(egret.Event.COMPLETE, this.onMovieEnd, this);
	}

	callback: mcCallback;

	doCallback(event: string): void {
		if (this.callback) {
			const f = this.callback;
			if (event === MOVIECLIP_EVENT_END || event === MOVIECLIP_EVENT_CANCEL) {
				// end or cancel!
				this.callback = null;
			}
			f(event);
		}
		if (this.cb) {
			const f = this.cb;
			if (
				event === MOVIECLIP_EVENT_END ||
				event === MOVIECLIP_EVENT_CANCEL ||
				(event === AniEventEnum.Hit && this.onhit)
			) {
				this.cb = null;
				f(event === MOVIECLIP_EVENT_CANCEL ? 0 : 1);
			}
		}
	}

	onMovieHit(ev: egret.MovieClipEvent): void {
		// ui.msglog("onMovieHit",ev.frameLabel)
		const event = ev.frameLabel.substr(1);
		if (event === AniEventEnum.Attack || event === AniEventEnum.Hit) {
			// if (this.onhit) {
			this.doCallback(event);
			// }
			// this.dispatchEventWith(event);
		} else {
			// other are sound event!
			const [sound, vol] = event.split(':');
			// music.playEvent(event); // 直接用了0.5~~~
			this.dispatchEventWith(MOVIECLIP_EVENT_SOUND, false, [sound, vol]);
		}
	}

	onMovieEnd(): void {
		this.doCallback(MOVIECLIP_EVENT_END);
		if (this.endRelease) {
			safeRemoveFromFather(this);
		}
	}

	gotoAndStopIndex(index: number): void {
		const frame = this.getFrameByIndex(index);
		this.gotoAndStop(frame);
	}

	gotoAndPlayIndex(index: number, times: number): void {
		const frame = this.getFrameByIndex(index);
		this.gotoAndPlay(frame, times);
	}

	endRelease = false;

	autoRelease() {
		this.endRelease = true;
	}

	// 当动画播放完毕，自动释放， for magic and buff!
	// onhit end 回调 1
	// cancel 回调 0
	cb: dramaCallback = null;

	onhit = 0;

	setDramaCallback(cb: dramaCallback, onhit: number) {
		this.cb = cb;
		this.onhit = onhit;
	}
}

// for AniWindow and BuidingWindow!
// 1. 修改key之后同时修改building!
class UIMovieClip extends eui.Group {
	mcDataFactory: MovieClipDataFactoryEx;

	mc: MovieClipEx;

	// mc:westMC
	tex: egret.Texture;

	data: MCFile;

	action: string;

	// 恢复到png的大小，只有单张图片才有效
	Resize() {
		if (this.mc && Object.keys(this.data.res).length === 1) {
			const r0 = this.data.res['0'];
			if (r0 && this.tex) {
				r0.w = this.tex.textureWidth;
				r0.h = this.tex.textureHeight;
				this.createMC(this.data);
			}
		}
	}

	XCenter() {
		if (this.mc) {
			this.data.offx = -this.data.mc['0'].frames[0].x - this.data.res[0].w / 2;
			this.createMC(this.data);
		}
	}

	YCenter() {
		if (this.mc) {
			this.data.offy = -this.data.mc['0'].frames[0].y - this.data.res[0].h / 2;
			this.createMC(this.data);
		}
	}

	offsetKey(offx: number, offy: number): void {
		if (this.mc) {
			egret.assert(this.data != null);
			this.data.offx = offx + (this.data.offx || 0);
			this.data.offy = offy + (this.data.offy || 0);
			// if (this.data.building)
			this.createMC(this.data);
		}
	}

	file: string;

	async addAni(file: string, data: MCFile, action = '0'): Promise<void> {
		this.file = file;

		const pngfile = file.replace('.json', '.png');
		console.log('addAni ', file, pngfile);

		// let str = await RES.getResByUrl(file,null,null,RES.ResourceItem.TYPE_TEXT)
		const tex = await RES.getResByUrl(pngfile);
		this.tex = tex;
		this.createMC(data, action);
	}

	onKeyFrame(e: egret.MovieClipEvent): void {
		// ui.msglog("onKeyFrame",e.frameLabel,this.mc.currentFrame)
	}

	changeAction(ani: string): void {
		if (this.action === ani) return;
		// this.action = ani;
		// this.clearMC();
		this.createMC(this.data, ani);
	}

	createMC(data: MCFile, action = '0'): void {
		this.clearMC();

		this.data = data;
		this.action = action;
		this.mcDataFactory = new MovieClipDataFactoryEx(data, this.tex);
		const mc = new MovieClipEx(this.mcDataFactory.generateMovieClipData(action));
		mc.setEvent(null);

		this.addChildAt(mc, 0);
		mc.play(1);
		// mc.addEventListener(egret.MovieClipEvent.FRAME_LABEL,this.onKeyFrame,this);

		this.mc = mc;
		this.createBoundbox();

		// this.addEventListener(mouse.MouseEvent.MOUSE_WHEEL, this.onMouseWheel, this);
	}

	// onMouseWheel(ev: egret.Event): void {
	// 	if (isCtrlDown()) {
	// 		game.ScaleDelta(this, ev.data);
	// 	}
	// }

	clearMC(): void {
		if (this.mc) {
			safeRemoveFromFather(this.mc);
			this.mc = null;
		}
		this.data = null;
	}

	refreshCache(fullfile: string): boolean {
		const pos = fullfile.indexOf('assets');
		if (pos < 0) {
			return false;
		}
		let file = fullfile.substr(pos + 6 + 1); // remove assets
		file = file.substr(0, file.length - 5); // remove .json
		file = FsUtils.window2linuxFile(file);

		McfMgr.ins().clearCache(file);
		const rtj = RES.destroyRes(`${getAssets() + file}.json`);
		console.log(`refreshCache ${file}`, rtj);

		game.GameMap.ins().onMCChange(file);

		return true;
	}

	// setBuilding(x0: number, y0: number, x1: number, y1: number): void {
	// 	if (x0 < x1) {
	// 		this.data.building = [x0, y0, x1, y1].map((a) => miscUtils.numberFix(a, 2));
	// 	} else {
	// 		this.data.building = [x1, y1, x0, y0].map((a) => miscUtils.numberFix(a, 2));
	// 	}
	// }

	save() {
		FsUtils.saveJson(this.file, this.data);
	}

	dot: egret.Bitmap;

	async createDot(): Promise<void> {
		if (this.dot) {
			return;
		}
		const img = new egret.Bitmap();
		img.texture = await loadTextureByUrl('reddot_png');
		img.anchorOffsetX = img.width / 2;
		img.anchorOffsetY = img.height / 2;
		img.x = 0;
		img.y = 0;
		img.touchEnabled = true;
		this.addChildAt(img, 999);
		this.dot = img;
		MainWindow.ins().bindDrag(img, false);
		this.dot.addEventListener(MainDrag.dragendEvent, this.onDragdotEnd, this);
		this.touchChildren = true;
		// this.createBoundbox();
	}

	shape: egret.Shape;

	createBoundbox() {
		if (this.shape) {
			this.removeChild(this.shape);
			this.shape = null;
		}
		const shape = new egret.Shape();
		const [l, t, r, b] = this.mc.getBoundSizeNow();
		const g = shape.graphics;
		g.clear();
		g.lineStyle(2, COLOR_PATH_LINE, 1);
		g.moveTo(l, t);
		g.lineTo(r, t);
		g.lineTo(r, b);
		g.lineTo(l, b);
		g.lineTo(l, t);
		// g.endFill();
		this.addChildAt(shape, 99);
		this.shape = shape;
	}

	onDragdotEnd(): void {
		const img = this.dot;
		const offx = img.x;
		const offy = img.y;

		this.offsetKey(-offx, -offy);

		img.x = 0;
		img.y = 0;
	}
}

class UICharMC extends eui.Group {
	mc: WestMCBody;

	action: string;

	dir = 0;

	shape = 0;

	async refresh(): Promise<void> {
		if (this.shape && this.action) {
			const dir2 = miscUtils.getMirrordir(this.dir);

			const mc = await createCharMC(this.shape, this.action, dir2);

			mc.scaleX = (dir2 !== this.dir ? -1 : 1) * Math.abs(mc.scaleX);

			mc.setEvent(null);
			mc.play(-1);
			this.addChild(mc);
			if (this.mc) {
				this.clearMC();
			}
			this.mc = mc;
		}
	}

	async setChar(shape: number): Promise<void> {
		this.shape = shape;
		return this.refresh();
	}

	async changeAction(action: string): Promise<void> {
		this.action = action;
		return this.refresh();
	}

	async setDir(dir: number): Promise<void> {
		if (!this.action) return null;
		const f = await CharMCFactorys.ins().getCharFactory(this.shape);
		const maxdir = f.maxDir(this.action);
		this.dir = dir % maxdir;
		if (this.dir < 0) this.dir += maxdir;
		return this.refresh();
	}

	clearMC(): void {
		if (this.mc) {
			safeRemoveFromFather(this.mc);
			this.mc = null;
		}
	}
}

class UIBaseChar extends eui.Group {
	char: game.BaseChar;

	constructor() {
		super();
		this.char = new game.BaseChar();
		this.char.onPlaySoundFunc = music.playOnce;
		this.addChild(this.char);
	}

	async refresh(): Promise<boolean> {
		const rt = await this.char._updateAction('refresh');
		if (rt) {
			const body2 = this.char.getBody();
			body2.setEvent(null);
		}
		return rt;
	}

	setChar(shape: number): void {
		this.char.body = shape;
	}

	changeAction(action: string): void {
		this.char.action = action;
	}

	setDir(dir: number): void {
		this.char.setDir(dir, true);
	}

	clearMC(): void {
		this.char.removeAll();
	}
}
